<?php

/**
 * @Author: Wang chunsheng  email:2192138785@qq.com
 * @Date:   2023-04-25 11:25:48
 * @Last Modified by:   Wang chunsheng  email:2192138785@qq.com
 * @Last Modified time: 2023-07-28 10:27:15
 */

namespace Common\WebSocket;


use Common\Context\WebsocketContext;
use Common\Helpers\DebugHelper;
use Common\Libs\Jwt\Exception;
use Common\Libs\Jwt\Jwt;
use Common\Models\sys\SysProject;
use Common\Services\JwtService;
use Common\Sign\CodeConst;
use Common\WebSocket\Utility\VerifyProject;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Router\DispatcherFactory;
use Hyperf\WebSocketServer\Collector\FdCollector;
use Hyperf\WebSocketServer\Server;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\SimpleCache\CacheInterface;
use Swoole\WebSocket\Server as WebSocketServer;
use Throwable;

class WebSocketService extends Server
{
    /**
     * @Inject
     * @var CacheInterface
     */
    #[Inject]
    protected CacheInterface $cache;

    #[Inject]
    protected ConfigInterface $config;

    /**
     * @var DispatcherFactory
     */
    #[Inject]
    protected DispatcherFactory $dispatcherFactory;

    #[Inject]
    protected RequestInterface $request;

    /**
     * @param WebSocketServer $server
     */
    public function onStart(WebSocketServer $server): void
    {
        DebugHelper::consoleWrite('打开-open');
        // 当有新 WebSocket 连接建立时会触发该方法
    }

    /**
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function onMessage($server, $frame): void
    {
        DebugHelper::consoleWrite('websocket-onMessage');
        if ($server instanceof WebSocketServer) {
            $fd = $frame->fd;
        } else {
            $fd = $this->getFd($server);
        }

        $client = $server->getClientInfo($frame->fd);
        // 或者 $client = $server->connection_info($frame->fd);
        if (isset($client['websocket_status'])) {
            echo "是websocket 连接";
        } else {
            echo "不是websocket 连接";
        }

        WebsocketContext::setServer($server);
        WebsocketContext::setFd($fd);

        $fdObj = FdCollector::get($fd);
        if (!$fdObj) {
            DebugHelper::consoleWrite('WebSocket: fd[%d] does not exist.', $fd);
            $this->logger->warning(sprintf('WebSocket: fd[%d] does not exist.', $fd));
            return;
        }

        $instance = $this->container->get($fdObj->class);

        if (!$instance instanceof OnMessageInterface) {
            DebugHelper::consoleWrite("{$instance} is not instanceof " . OnMessageInterface::class);
            $this->logger->warning("{$instance} is not instanceof " . OnMessageInterface::class);
            return;
        }

        try {
            $data = json_decode($frame->data, true);
            if (!is_array($data)) {
                $server->push($frame->fd, '消息内容必须是json');
                return;
            }

            if (!key_exists('type', $data)) {
                $server->push($frame->fd, 'type 不能为空');
                return;
            }
            /**
             * 获取websocket 的ip
             */
            $ip = $client['remote_ip'];
            var_dump($ip);
            DebugHelper::consoleWrite('WebSocketService-解析数据-socket', $data);




            if (!key_exists('app_id', $data)) {
                $server->push($frame->fd, 'app_id 不能为空');
                return;
            }

            $project_id = $this->cache->get($data['app_id']);
            if (empty($project_id)) {
                $SysProject = new SysProject();
                $project_id = $SysProject->query()->where(['app_id' => $data['app_id']])->value('id');
                if (empty($project_id)){
                    $server->push($frame->fd, 'app_id 错误,项目不存在');
                    return;
                }
                $this->cache->set($data['app_id'], $project_id);
            }
            DebugHelper::consoleWrite('全局 project_id',[
                'project_id' => $project_id
            ]);

            WebsocketContext::setProjectId($project_id);

            //心跳
            if ($data['type'] === 'heart') {
                $Res = [
                    'code' => 200,
                    'type' => 'heart_res',
                    'message' => '心跳成功'
                ];
                $server->push($frame->fd, json_encode($Res, JSON_UNESCAPED_UNICODE));
                return;
            }


            /*
             * 特殊请求直接放行
            */
            if(in_array($data['type'],['platform','register','login','welcome','flush-token'])){
                $instance->onMessage($server, $frame);
//                return;
            }
            DebugHelper::consoleWrite('WebSocketService-解析数据-放行失败', $data);

            if (!key_exists('sign', $data)) {
                $server->push($frame->fd, 'sign 不能为空');
                return;
            }

            if (!key_exists('timestamp', $data)) {
                $server->push($frame->fd, 'timestamp 不能为空');
                return;
            }

            WebsocketContext::setContent($data);

            if ($data['type'] === 'operation') {
                //全局缓存绑定设备和fd
                $device_id = $data['device_id'];
                $websocket_cache_key = "websocket_fd：" . $device_id;
                DebugHelper::consoleWrite('写入缓存', [
                    'device_id' => $device_id,
                    'fd' => $frame->fd,
                    'k' => $websocket_cache_key
                ]);
                $this->cache->set($websocket_cache_key, $frame->fd);
            }

            if (in_array($data['type'], ['operation_success', 'operation_error'])) {
                $device_id = (int)$data['data']['device_id'];
                $websocket_cache_key = "websocket_fd：" . $device_id;

                DebugHelper::consoleWrite('WebSocketService-准备响应操作', $data);
                if ($device_id) {
                    $websocket_fd = $this->cache->get($websocket_cache_key);
                    DebugHelper::consoleWrite('WebSocketService-准备响应操作-fd', [
                        'device_id' => $device_id,
                        'fd' => $websocket_fd,
                        'k' => $websocket_cache_key
                    ]);
                    $server->push($websocket_fd, json_encode($data, JSON_UNESCAPED_UNICODE));
                    return;
                }
            }

            DebugHelper::consoleWrite('WebSocketService-握手-服务', $data);

            //token校验
//            $this->checkAccessToken($server, $frame->fd, $data);

            // 签名验证开始
            $checkSign = VerifyProject::verifySign($data);
            DebugHelper::consoleWrite('WebSocketService-验证内容反馈', $checkSign);
            if ($checkSign['code'] === (int)CodeConst::CODE_90011) {
                DebugHelper::consoleWrite('WebSocketService-验证完成');
                DebugHelper::consoleWrite('WebSocketService-传递onMessage', $data);
            } else {
                $data = [
                    'code' => 405,
                    'type' => 'error',
                    'message' => $checkSign['message'],
                    'data' => $checkSign
                ];
                $server->push($frame->fd, json_encode($data, JSON_UNESCAPED_UNICODE));
                return;
            }

            $instance->onMessage($server, $frame);
        } catch (Throwable $exception) {
            DebugHelper::consoleWrite('websocket服务错误', [$exception->getMessage(),$exception->getFile(), $exception->getLine()]);
            $this->logger->error((string)$exception);
        }
    }

    /**
     * 校验token
     * @param $server
     * @param $fd
     * @param $data
     * @return void
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     * @throws Exception
     */
    public function checkAccessToken($server, $fd, $data): void
    {
        //token验证
        $token = $data['accessToken'] ?? '';
        if (empty($token)) {
            $data = [
                'code' => 402,
                'type' => 'error',
                'message' => 'accessToken 不能为空'
            ];
            $server->push($fd, json_encode($data, JSON_UNESCAPED_UNICODE));
            return;
        }
        $config = $this->config;
        // 验证 JWT 是否合法
        $secret = $config->get('jwt.SecretKey');
        $tokenStr = JwtService::decrypt_ecb($token);
        $jwtObject = Jwt::getInstance()->setSecretKey($secret)->decode($tokenStr);

        $status = $jwtObject->getStatus();

        $Iss = $jwtObject->getIss();

        if ($Iss != $config->get('jwt.Iss')) {
            $data = [
                'code' => 401,
                'type' => 'error',
                'message' => '非法请求',
                'data' => []
            ];
            $server->push($fd, json_encode($data, JSON_UNESCAPED_UNICODE));
            return;
        }

        // 如果encode设置了秘钥,decode 的时候要指定
        switch ($status) {
            case  1:
                $userInfo = JwtService::getUser($token);
                DebugHelper::consoleWrite('挂载前数据', $userInfo['member']);
                // 挂载数据 到 $request 对象上
                WebsocketContext::setUserInfo($userInfo);
                WebsocketContext::setMemberId($userInfo['member']['member_id']);
                WebsocketContext::setMember($userInfo['member']);
                break;
            case  -1:
                $data = [
                    'code' => 402,
                    'type' => 'error',
                    'message' => 'token已失效'
                ];
                $server->push($fd, json_encode($data, JSON_UNESCAPED_UNICODE));
                return;
            case  -2:
                $data = [
                    'code' => 403,
                    'type' => 'error',
                    'message' => 'token已过期'
                ];
                $server->push($fd, json_encode($data, JSON_UNESCAPED_UNICODE));
                return;
        }
    }
}
